/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.library.bubbleview;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Path;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Shader;
import ohos.agp.render.PixelMapShader;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.RectFloat;
import ohos.media.image.PixelMap;

/**
 * 绘制气泡路径，分为两种：1、背景，2、使用着色器方式处理图片
 *
 * @since 2021-06-24
 */
public final class BubbleElement extends ShapeElement {
    private static final int CONSTANTS_0 = 0;
    private static final int CONSTANTS_2 = 2;
    private static final int CONSTANTS_90 = 90;
    private static final int CONSTANTS_180 = 180;
    private static final int CONSTANTS_270 = 270;

    // 浮点矩形
    private final RectFloat mRect;

    // 路径
    private final Path mPath = new Path();

    // 画笔
    private final Paint mPaint = new Paint();

    // 箭头宽度
    private final float mArrowWidth;

    // 圆角宽度（四角）
    private final float mAngle;

    // 箭头高度
    private final float mArrowHeight;

    // 箭头是否在边的中心
    private final boolean isArrowCenter;

    // 箭头位置（上下左右）
    private final ArrowLocation mArrowLocation;

    // 箭头
    private float mArrowPosition;

    // 气泡背景颜色
    private final Color bubbleColor;

    // 气泡类型
    private final BubbleType bubbleType;

    // 画布
    private final Canvas mCanvas;

    // 气泡背景图片
    private final PixelMap bubblePixelMap;
    private PixelMapShader mPixelMapShader;

    private BubbleElement(Builder builder) {
        this.mRect = builder.mRect;
        this.mAngle = builder.mAngle;
        this.mArrowHeight = builder.mArrowHeight;
        this.mArrowWidth = builder.mArrowWidth;
        this.mArrowPosition = builder.mArrowPosition;
        this.bubbleColor = builder.bubbleColor;
        this.mArrowLocation = builder.mArrowLocation;
        this.isArrowCenter = builder.isArrowCenter;
        this.bubblePixelMap = builder.bubblePixelMap;
        this.bubbleType = builder.bubbleType;
        this.mCanvas = builder.mCanvas;
        init();
    }

    private void init() {
        // 根据方向设置路径
        setPath(mArrowLocation, mPath);

        if (bubbleType == BubbleType.COLOR) {
            // 设置形状为路径
            setShape(PATH);

            // 设置背景颜色
            setRgbColor(RgbColor.fromArgbInt(bubbleColor.getValue()));
        } else {
            setShaderType(LINEAR_GRADIENT_SHADER_TYPE);
            if (bubblePixelMap == null) {
                return;
            }

            if (mPixelMapShader == null) {
                mPixelMapShader = new PixelMapShader(new PixelMapHolder(bubblePixelMap),
                        Shader.TileMode.CLAMP_TILEMODE, Shader.TileMode.CLAMP_TILEMODE);
            }
            setShaderMatrix();
            mPaint.setAntiAlias(true);
            mPaint.setShader(mPixelMapShader, Paint.ShaderType.LINEAR_SHADER);
            mCanvas.drawPath(mPath, mPaint);
        }
    }

    private void setPath(ArrowLocation arrowLocation, Path path) {
        switch (arrowLocation) {
            case LEFT:
                setLeftPath(mRect, path);
                break;
            case RIGHT:
                setRightPath(mRect, path);
                break;
            case TOP:
                setTopPath(mRect, path);
                break;
            case BOTTOM:
                setBottomPath(mRect, path);
                break;
            default:
                break;
        }
        setPath(mPath);
    }

    private void setLeftPath(RectFloat rect, Path path) {
        if (isArrowCenter) {
            mArrowPosition = (rect.bottom - rect.top) / CONSTANTS_2 - mArrowWidth / CONSTANTS_2;
        }

        path.moveTo(mArrowWidth + rect.left + mAngle, rect.top);
        path.lineTo(rect.getWidth() - mAngle, rect.top);
        path.arcTo(new RectFloat(rect.right - mAngle, rect.top, rect.right,
                mAngle + rect.top), CONSTANTS_270, CONSTANTS_90);
        path.lineTo(rect.right, rect.bottom - mAngle);
        path.arcTo(new RectFloat(rect.right - mAngle, rect.bottom - mAngle,
                rect.right, rect.bottom), CONSTANTS_0, CONSTANTS_90);
        path.lineTo(rect.left + mArrowWidth + mAngle, rect.bottom);
        path.arcTo(new RectFloat(rect.left + mArrowWidth, rect.bottom - mAngle,
                mAngle + rect.left + mArrowWidth, rect.bottom), CONSTANTS_90, CONSTANTS_90);
        path.lineTo(rect.left + mArrowWidth, mArrowHeight + mArrowPosition);
        path.lineTo(rect.left, mArrowPosition + mArrowHeight / CONSTANTS_2);
        path.lineTo(rect.left + mArrowWidth, mArrowPosition);
        path.lineTo(rect.left + mArrowWidth, rect.top + mAngle);
        path.arcTo(new RectFloat(rect.left + mArrowWidth, rect.top, mAngle
                + rect.left + mArrowWidth, mAngle + rect.top), CONSTANTS_180, CONSTANTS_90);
        path.close();
    }

    private void setTopPath(RectFloat rect, Path path) {
        if (isArrowCenter) {
            mArrowPosition = (rect.right - rect.left) / CONSTANTS_2 - mArrowWidth / CONSTANTS_2;
        }

        path.moveTo(rect.left + Math.min(mArrowPosition, mAngle), rect.top + mArrowHeight);
        path.lineTo(rect.left + mArrowPosition, rect.top + mArrowHeight);
        path.lineTo(rect.left + mArrowWidth / CONSTANTS_2 + mArrowPosition, rect.top);
        path.lineTo(rect.left + mArrowWidth + mArrowPosition, rect.top + mArrowHeight);
        path.lineTo(rect.right - mAngle, rect.top + mArrowHeight);

        path.arcTo(new RectFloat(rect.right - mAngle,
                rect.top + mArrowHeight, rect.right, mAngle + rect.top + mArrowHeight), CONSTANTS_270, CONSTANTS_90);
        path.lineTo(rect.right, rect.bottom - mAngle);

        path.arcTo(new RectFloat(rect.right - mAngle, rect.bottom - mAngle,
                rect.right, rect.bottom), CONSTANTS_0, CONSTANTS_90);
        path.lineTo(rect.left + mAngle, rect.bottom);

        path.arcTo(new RectFloat(rect.left, rect.bottom - mAngle,
                mAngle + rect.left, rect.bottom), CONSTANTS_90, CONSTANTS_90);
        path.lineTo(rect.left, rect.top + mArrowHeight + mAngle);
        path.arcTo(new RectFloat(rect.left, rect.top + mArrowHeight, mAngle
                + rect.left, mAngle + rect.top + mArrowHeight), CONSTANTS_180, CONSTANTS_90);
        path.close();
    }

    private void setRightPath(RectFloat rect,Path path) {
        if (isArrowCenter) {
            mArrowPosition = (rect.bottom - rect.top) / CONSTANTS_2 - mArrowWidth / CONSTANTS_2;
        }

        path.moveTo(rect.left + mAngle, rect.top);
        path.lineTo(rect.getWidth() - mAngle - mArrowWidth, rect.top);
        path.arcTo(new RectFloat(rect.right - mAngle - mArrowWidth,
                rect.top, rect.right - mArrowWidth, mAngle + rect.top), CONSTANTS_270, CONSTANTS_90);
        path.lineTo(rect.right - mArrowWidth, mArrowPosition);
        path.lineTo(rect.right, mArrowPosition + mArrowHeight / CONSTANTS_2);
        path.lineTo(rect.right - mArrowWidth, mArrowPosition + mArrowHeight);
        path.lineTo(rect.right - mArrowWidth, rect.bottom - mAngle);

        path.arcTo(new RectFloat(rect.right - mAngle - mArrowWidth, rect.bottom - mAngle,
                rect.right - mArrowWidth, rect.bottom), CONSTANTS_0, CONSTANTS_90);
        path.lineTo(rect.left + mArrowWidth, rect.bottom);

        path.arcTo(new RectFloat(rect.left, rect.bottom - mAngle,
                mAngle + rect.left, rect.bottom), CONSTANTS_90, CONSTANTS_90);

        path.arcTo(new RectFloat(rect.left, rect.top, mAngle
                + rect.left, mAngle + rect.top), CONSTANTS_180, CONSTANTS_90);
        path.close();
    }

    private void setBottomPath(RectFloat rect, Path path) {
        if (isArrowCenter) {
            mArrowPosition = (rect.right - rect.left) / CONSTANTS_2 - mArrowWidth / CONSTANTS_2;
        }
        path.moveTo(rect.left + mAngle, rect.top);
        path.lineTo(rect.getWidth() - mAngle, rect.top);
        path.arcTo(new RectFloat(rect.right - mAngle,
                rect.top, rect.right, mAngle + rect.top), CONSTANTS_270, CONSTANTS_90);

        path.lineTo(rect.right, rect.bottom - mArrowHeight - mAngle);
        path.arcTo(new RectFloat(rect.right - mAngle, rect.bottom - mAngle - mArrowHeight,
                rect.right, rect.bottom - mArrowHeight), CONSTANTS_0, CONSTANTS_90);

        path.lineTo(rect.left + mArrowWidth + mArrowPosition, rect.bottom - mArrowHeight);
        path.lineTo(rect.left + mArrowPosition + mArrowWidth / CONSTANTS_2, rect.bottom);
        path.lineTo(rect.left + mArrowPosition, rect.bottom - mArrowHeight);
        path.lineTo(rect.left + Math.min(mAngle, mArrowPosition), rect.bottom - mArrowHeight);

        path.arcTo(new RectFloat(rect.left, rect.bottom - mAngle - mArrowHeight,
                mAngle + rect.left, rect.bottom - mArrowHeight), CONSTANTS_90, CONSTANTS_90);
        path.lineTo(rect.left, rect.top + mAngle);
        path.arcTo(new RectFloat(rect.left, rect.top, mAngle
                + rect.left, mAngle + rect.top), CONSTANTS_180, CONSTANTS_90);
        path.close();
    }

    private void setShaderMatrix() {
        Matrix mShaderMatrix = new Matrix();
        int mBitmapWidth = bubblePixelMap.getImageInfo().size.width;
        int mBitmapHeight = bubblePixelMap.getImageInfo().size.height;
        float scaleX = getWidth() / (float) mBitmapWidth;
        float scaleY = getHeight() / (float) mBitmapHeight;
        mShaderMatrix.postScale(scaleX, scaleY);
        mShaderMatrix.postTranslate(mRect.left, mRect.top);
        mPixelMapShader.setShaderMatrix(mShaderMatrix);
    }

    @Override
    public int getWidth() {
        return (int) mRect.getWidth();
    }

    @Override
    public int getHeight() {
        return (int) mRect.getHeight();
    }

    /**
     * Builder
     *
     * @since 2021-06-24
     */
    public static class Builder {
        /**
         * 默认的箭头宽度
         */
        public static final float DEFAULT_ARROW_WITH = 25;
        /**
         * 默认的箭头高度
         */
        public static final float DEFAULT_ARROW_HEIGHT = 25;
        /**
         * 默认的圆角半径
         */
        public static final float DEFAULT_ANGLE = 20;
        /**
         * 默认的箭头位置
         */
        public static final float DEFAULT_ARROW_POSITION = 20;
        /**
         * 默认的气泡颜色
         */
        public static final Color DEFAULT_BUBBLE_COLOR = Color.RED;
        private RectFloat mRect;
        private float mArrowWidth = DEFAULT_ARROW_WITH;
        private float mAngle = DEFAULT_ANGLE;
        private float mArrowHeight = DEFAULT_ARROW_HEIGHT;
        private float mArrowPosition = DEFAULT_ARROW_POSITION;
        private Color bubbleColor = DEFAULT_BUBBLE_COLOR;
        private PixelMap bubblePixelMap;
        private BubbleType bubbleType = BubbleType.COLOR;
        private Canvas mCanvas;
        private ArrowLocation mArrowLocation = ArrowLocation.LEFT;
        private boolean isArrowCenter;

        /**
         * 设置矩形
         *
         * @param rect 矩形
         * @return 当前对象
         */
        public Builder rect(RectFloat rect) {
            this.mRect = rect;
            return this;
        }

        /**
         * 设置箭头宽度
         *
         * @param arrowWidth 箭头宽度
         * @return 当前对象
         */
        public Builder arrowWidth(float arrowWidth) {
            this.mArrowWidth = arrowWidth;
            return this;
        }

        /**
         * 设置圆角半径
         *
         * @param angle 圆角半径
         * @return 当前对象
         */
        public Builder angle(float angle) {
            this.mAngle = angle * CONSTANTS_2;
            return this;
        }

        /**
         * 设置箭头高度
         *
         * @param arrowHeight 箭头高度
         * @return 当前对象
         */
        public Builder arrowHeight(float arrowHeight) {
            this.mArrowHeight = arrowHeight;
            return this;
        }

        /**
         * 设置箭头距边位置
         *
         * @param arrowPosition 箭头距边位置
         * @return 当前对象
         */
        public Builder arrowPosition(float arrowPosition) {
            this.mArrowPosition = arrowPosition;
            return this;
        }

        /**
         * 设置气泡颜色
         *
         * @param bubbleColorParam 气泡颜色
         * @return 当前对象
         */
        public Builder bubbleColor(Color bubbleColorParam) {
            this.bubbleColor = bubbleColorParam;
            bubbleType(BubbleType.COLOR);
            return this;
        }

        /**
         * 设置图片
         *
         * @param bubblePixelMapParam 图片
         * @return 当前对象
         */
        public Builder bubblePixelMap(PixelMap bubblePixelMapParam) {
            this.bubblePixelMap = bubblePixelMapParam;
            bubbleType(BubbleType.PIXEL_MAP);
            return this;
        }

        /**
         * 设置箭头位置
         *
         * @param arrowLocationParam 箭头位置
         * @return 当前对象
         */
        public Builder arrowLocation(ArrowLocation arrowLocationParam) {
            this.mArrowLocation = arrowLocationParam;
            return this;
        }

        /**
         * 设置气泡类型
         *
         * @param bubbleTypeParam 气泡类型：背景或图片
         * @return 当前对象
         */
        public Builder bubbleType(BubbleType bubbleTypeParam) {
            this.bubbleType = bubbleTypeParam;
            return this;
        }

        /**
         * 设置箭头是否中心
         *
         * @param isArrowCenterParam 箭头是否中心
         * @return 当前对象
         */
        public Builder arrowCenter(boolean isArrowCenterParam) {
            this.isArrowCenter = isArrowCenterParam;
            return this;
        }

        /**
         * 设置画布
         *
         * @param canvas 画布
         * @return 当前对象
         */
        public Builder setCanvas(Canvas canvas) {
            this.mCanvas = canvas;
            return this;
        }

        /**
         * build
         *
         * @return BubbleElement对象
         */
        public BubbleElement build() {
            BubbleElement bubbleElement = null;
            if (mRect != null) {
                bubbleElement = new BubbleElement(this);
            }
            return bubbleElement;
        }
    }

    /**
     * 箭头方向
     *
     * @since 2021-06-24
     */
    public enum ArrowLocation {
        /**
         * 方向：左
         */
        LEFT("left", 0x00),
        /**
         * 方向：右
         */
        RIGHT("right", 0x01),
        /**
         * 方向：上
         */
        TOP("top", 0x02),
        /**
         * 方向：下
         */
        BOTTOM("bottom", 0x03);

        private final String mName;
        private final int mValue;

        ArrowLocation(String name, int value) {
            this.mName = name;
            this.mValue = value;
        }

        /**
         * 根据名称获取对象
         *
         * @param name 名称
         * @return 对象
         */
        public static ArrowLocation mapIntToName(String name) {
            for (ArrowLocation value : ArrowLocation.values()) {
                if (value.getName().equals(name)) {
                    return value;
                }
            }
            return getDefault();
        }

        /**
         * 默认的箭头方向
         *
         * @return 默认的箭头方向
         */
        public static ArrowLocation getDefault() {
            return LEFT;
        }

        /**
         * getIntValue
         *
         * @return 数值
         */
        public int getIntValue() {
            return mValue;
        }

        /**
         * getName
         *
         * @return 名称
         */
        public String getName() {
            return mName;
        }
    }

    /**
     * 气泡类型
     *
     * @since 2021-06-24
     */
    public enum BubbleType {
        /**
         * 设置背景
         */
        COLOR,
        /**
         * 图片
         */
        PIXEL_MAP
    }
}
